package com.study.hotreloadsample.hotReload;

import android.app.Application;
import android.content.Context;
import android.content.res.Resources;
import android.os.Build;

import java.io.File;
import java.lang.reflect.Field;
import java.util.List;

import dalvik.system.DexFile;
import dalvik.system.PathClassLoader;

/**
 * 作者： zjf 10/11/20 1:54 PM
 * 参考：
 * 描述：
 */
public class NewClassLoader {

    private static final class DispatchClassLoader extends ClassLoader {
        private final String mApplicationClassName;
        private final ClassLoader mOldClassLoader;
        private ClassLoader mNewClassLoader;

        public DispatchClassLoader(String mApplicationClassName, ClassLoader mOldClassLoader) {
            //设置父 类加载器
            super(ClassLoader.getSystemClassLoader());

            this.mApplicationClassName = mApplicationClassName;
            this.mOldClassLoader = mOldClassLoader;
        }

        void setNewClassLoader(ClassLoader classLoader) {
            this.mNewClassLoader = classLoader;
        }

        private final ThreadLocal<Boolean> threadLocal = new ThreadLocal<Boolean>() {
            @Override
            protected Boolean initialValue() {
                return false;
            }
        };

        /**
         * 寻找类的方法
         *
         * @param name
         * @return
         * @throws ClassNotFoundException
         */
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            //由于双亲委托机制,可能会引起无限的递归引起StackOverFlow,所以加一个判断避免栈溢出
            if (threadLocal.get()) {
                return null;
            }
            //如果是Application类,使用原来的类加载器进行加载
            if (name.equals(mApplicationClassName)) {
                return findClass(mOldClassLoader, name);
            }
            try {
                //在自定义的类加载器能加载的话就加在
                return findClass(mNewClassLoader, name);
            } catch (Exception e) {
                //异常后使用系统的类加载器加载
                return findClass(mOldClassLoader, name);
            }
        }


        protected Class<?> findClass(ClassLoader classLoader, String name) throws ClassNotFoundException {
            try {
                threadLocal.set(true);
                return classLoader.loadClass(name);
            } finally {
                threadLocal.set(false);
            }
        }
    }

    public static void inject(Application app, ClassLoader oldClassLoader, List<File> patchList) throws Throwable {
        //作为自定义加载器的父加载器
        DispatchClassLoader dispatchClassLoader = new DispatchClassLoader(app.getClass().getName(), oldClassLoader);

        ClassLoader newClassLoader = createNewClassLoader(app, oldClassLoader, dispatchClassLoader, patchList);

        dispatchClassLoader.setNewClassLoader(newClassLoader);

        doInject(app, newClassLoader);
    }


    /**
     * 创建自定义的类加载器
     */
    private static ClassLoader createNewClassLoader(Context context, ClassLoader oldClassLoader,
                                                    ClassLoader dispatchClassLoader, List<File> patchs) throws Throwable {
        //得到pathList
        Field pathListField = ReflectUtil.findField(oldClassLoader, "pathList");
        Object oldPathList = pathListField.get(oldClassLoader);

        //dexElements
        Field dexElementsField = ReflectUtil.findField(oldPathList, "dexElements");
        Object[] oldDexElements = (Object[]) dexElementsField.get(oldPathList);

        //从Element上得到 dexFile
        Field dexFileField = ReflectUtil.findField(oldDexElements[0], "dexFile");

        // 获得原始的dexPath用于构造classloader
        StringBuilder dexPathBuilder = new StringBuilder();
        String packageName = context.getPackageName();
        boolean isFirstItem = true;
        for (File patch : patchs) {
            if (isFirstItem) {
                isFirstItem = false;
            } else {
                dexPathBuilder.append(File.pathSeparator);
            }
            dexPathBuilder.append(patch.getAbsolutePath());
        }
        for (Object oldDexElement : oldDexElements) {
            String dexPath = null;
            DexFile dexFile = (DexFile) dexFileField.get(oldDexElement);
            if (dexFile != null) {
                dexPath = dexFile.getName();
            }
            if (dexPath == null || dexPath.isEmpty()) {
                continue;
            }
            if (!dexPath.contains("/" + packageName)) {
                continue;
            }
            if (isFirstItem) {
                isFirstItem = false;
            } else {
                dexPathBuilder.append(File.pathSeparator);
            }
            dexPathBuilder.append(dexPath);
        }
        final String combinedDexPath = dexPathBuilder.toString();

        //  app的native库（so） 文件目录 用于构造classloader
        Field nativeLibraryDirectoriesField = ReflectUtil.findField(oldPathList, "nativeLibraryDirectories");
        List<File> oldNativeLibraryDirectories = (List<File>) nativeLibraryDirectoriesField.get(oldPathList);


        StringBuilder libraryPathBuilder = new StringBuilder();
        isFirstItem = true;
        for (File libDir : oldNativeLibraryDirectories) {
            if (libDir == null) {
                continue;
            }
            if (isFirstItem) {
                isFirstItem = false;
            } else {
                libraryPathBuilder.append(File.pathSeparator);
            }
            libraryPathBuilder.append(libDir.getAbsolutePath());
        }

        String combinedLibraryPath = libraryPathBuilder.toString();

        //创建自己的类加载器
        ClassLoader result = new PathClassLoader(combinedDexPath, combinedLibraryPath, dispatchClassLoader);
        ReflectUtil.findField(oldPathList, "definingContext").set(oldPathList, result);
        ReflectUtil.findField(result, "parent").set(result, dispatchClassLoader);
        return result;
    }

    private static void doInject(Application app, ClassLoader classLoader) throws Throwable {
        Thread.currentThread().setContextClassLoader(classLoader);

        Context baseContext = (Context) ReflectUtil.findField(app, "mBase").get(app);
        Object basePackageInfo = ReflectUtil.findField(baseContext, "mPackageInfo").get(baseContext);
        ReflectUtil.findField(basePackageInfo, "mClassLoader").set(basePackageInfo, classLoader);

        if (Build.VERSION.SDK_INT < 27) {
            Resources res = app.getResources();
            try {
                ReflectUtil.findField(res, "mClassLoader").set(res, classLoader);

                final Object drawableInflater = ReflectUtil.findField(res, "mDrawableInflater").get(res);
                if (drawableInflater != null) {
                    ReflectUtil.findField(drawableInflater, "mClassLoader").set(drawableInflater, classLoader);
                }
            } catch (Throwable ignored) {
                // Ignored.
            }
        }
    }

}
